{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "backAVIforMMD.ipynb",
      "provenance": [],
      "collapsed_sections": [],
      "toc_visible": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tFapH7r7h4EO"
      },
      "source": [
        "# MMD用背景AVI生成ツール"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "P3qroHxHUnD0"
      },
      "source": [
        "このツールでは、Googleドライブにアップロードされた動画ファイルを、MMDで読み込める30fpsのAVIファイルに変換して出力します。\n",
        "\n",
        "初めての方はcolabの使い方セクションで、使い方に慣れて下さい。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-cl6P0V1jDze"
      },
      "source": [
        "# colabの使い方"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v0k-zhnUjYR8"
      },
      "source": [
        "※ここでは、Jupyter Notebookの操作方法も合わせて「colabの使い方」として扱います。そのため、一部正確な表現ではありません。ご了承下さい。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Atf--UGaiEAj"
      },
      "source": [
        "## colab (Google Colaboratory)とは"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nt6wtv5siOEj"
      },
      "source": [
        "\n",
        "> Google Colaboratory(略称：colab (コラボ)) は、完全にクラウドで実行される Jupyter ノートブック環境です。設定不要で、無料でご利用になれます。\n",
        "\n",
        "> Jupyter Notebook (ジュピター・ノートブック) とは、ノートブックと呼ばれる形式で作成したプログラムを実行し、実行結果を記録しながら、データの分析作業を進めるためのツールです。\n",
        "\n",
        "Chrome もしくは Firefoxがあれば、後は全部Googleのクラウド上で、ツールを実行することができます。\n",
        "実行結果のVMD等は、ご自分のローカル環境にダウンロードする事ができます。\n",
        "\n",
        "使い方に少しクセがあるので、この導入セクションで練習してみてから、ツールを始めてください。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "h-mK3nMeFMcb"
      },
      "source": [
        "## セル（プログラム）の実行"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3SLEmu5ujvZ5"
      },
      "source": [
        "colabは「セル」と呼ばれる単位ごとに処理を実行することができます。\n",
        "\n",
        "この下に記載している、薄いグレーの欄の`[ ]`の部分にカーソルを合わせてみてください。\n"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "tfHWt-8dv9nU"
      },
      "source": [
        "!echo \"Hello MMD Auto Trace!\""
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JrARkkuKkPpo"
      },
      "source": [
        "\n",
        "![セル1](https://drive.google.com/uc?export=view&id=1Iq65BhLYQAx8vBBgdfEsLO0VeOVMjJuS)\n",
        "\n",
        "カーソルを合わせると、三角の再生ボタンが表示されたのがお分かりでしょうか。\n",
        "\n",
        "![セル2](https://drive.google.com/uc?export=view&id=1_542_9rRF48Fr9TkLlERrT-1JCLG6VLU)\n",
        "\n",
        "この三角ボタンをクリックすると、セルが実行され、中に記載されているコードが順次処理されます。\n",
        "\n",
        "※ページを開いて、最初の実行時には、ノートブックの実行許可が求められます。\n",
        "\n",
        "\n",
        "![確認ダイアログ](https://drive.google.com/uc?export=view&id=1PTNx_cZ0YY8HVN4i28F8jALK87X_MnWp)\n",
        "\n",
        "ランタイムリセットのチェックが入っている場合、ランタイムのリセットの確認ダイアログが出ます\n",
        "\n",
        "**※リセットを行うと、環境がすべて破棄されます。インストール済（実行編で準備が終わっている）の場合はチェックを外して下さい。**\n",
        "\n",
        "![ランタイムのリセット](https://drive.google.com/uc?export=view&id=11pJrO0-boAmLH0sheH-G1bshA3goETPq)\n",
        "\n",
        "これでノートブックのセル（プログラム）を実行することができるようになりました。\n",
        "\n",
        "\n",
        "![セル3](https://drive.google.com/uc?export=view&id=19c52CCO_fsT7Xp86ArhUjXtcdaojrBvj)\n",
        "\n",
        "三角の周りの点線の円が回っている場合、そのセルは実行中もしくは実行待ちです。\n",
        "\n",
        "![タブ](https://drive.google.com/uc?export=view&id=1tW5V1O4z5Lftq-dLMn-7kB5lWCTrQ88i)\n",
        "\n",
        "実行中の場合、ブラウザタブのアイコンがグレーになります。\n",
        "\n",
        "![セル4](https://drive.google.com/uc?export=view&id=1PEcmTlmw_Y4PWWBVFsv7OHJS9_d9GEIr)\n",
        "\n",
        "実行結果はセルの下に出力されます。\n",
        "たったこれだけで、プログラムが実行されます。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tw2ZrQyoFR-s"
      },
      "source": [
        "## Googleドライブとの連携（読み込み）"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7uOqWO5KFVOv"
      },
      "source": [
        "このツールでは、Googleドライブにアップロードした動画を処理します。\n",
        "\n",
        "ここでは、Googleドライブとの連携を実際に確認してみてください。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Uof2npq1IsQT"
      },
      "source": [
        "1．Googleドライブのルートフォルダに、\"input.mp4\" という動画ファイルをアップロードしてください。\n",
        "\n",
        "（下のキャプチャは自動トレースの流用なので、autotraceフォルダ以下に配置してます）\n",
        "\n",
        "ミソジサラリーマン様\\([@keriwaza](https://twitter.com/keriwaza)\\) より、トレース用素材動画を提供していただきました。ご自由にDLしてお使いください。\n",
        "\n",
        "[足滑りシーン動画(Googleドライブ)](https://drive.google.com/open?id=1kJrq3H8B_LrJDNfR4VA4BhfyrDnjBWvJ)\n",
        "\n",
        "※動画を公開する場合には、クレジットに「`モーション：ミソジサラリーマン様(@keriwaza)`」を追記してください。\n",
        "\n",
        "\n",
        "![アップロード](https://drive.google.com/uc?export=view&id=1gq-m_MUqmLe-I8VywuqWKaSFxMHejiqO)\n",
        "\n",
        "2．下のセルを実行すると、URLと入力欄が表示されます。\n",
        "\n",
        "![Googleドライブ認証](https://drive.google.com/uc?export=view&id=1Mu3AencjMOHc3ZMBxqAaTpBFixb1FZvW)\n",
        "\n",
        "3．URLをクリックすると、新しいタブが開いて、colabと連携するアカウントを聞かれます（聞かれなかった場合、次に進んで下さい）\n",
        "\n",
        "![アカウントの選択](https://drive.google.com/uc?export=view&id=1uS2HtoclPP-d4HF2m9iZ6F2ZuwGB3pXb)\n",
        "\n",
        "4．アカウントを選ぶと、リクエストの許可確認画面が出ますので、アカウントを確認して、「許可」を選択してください。\n",
        "\n",
        "![代替テキスト](https://drive.google.com/uc?export=view&id=1erLq1ZX-MJFFD9K6aWOZYOYzI2-IN_Fw)\n",
        "\n",
        "5．許可すると、認証コードの横にコピーボタンが表示されますので、コピーボタンをクリックして下さい。\n",
        "\n",
        "![認証コード](https://drive.google.com/uc?export=view&id=10fFOn8YSBdq0H5Q9oHDTYln4pWcOhdeI)\n",
        "\n",
        "6．colabの画面に戻ってきて、URLの下の入力欄にカーソルを合わせて、「Ctrl+V」で貼り付けて下さい。文字列は・で表示されます。\n",
        "\n",
        "![認証コード貼り付け](https://drive.google.com/uc?export=view&id=1vmAeCeWzugW211K-uJ4evb9wCrqPnR_Q)\n",
        "\n",
        "7．貼り付けたら、Enterをクリックします。認証には少し時間がかかります。\n",
        "\n",
        "8．下記のように、フォルダの中身とファイルサイズが取得できていれば成功です。\n",
        "\n",
        "![連携結果](https://drive.google.com/uc?export=view&id=1dZu9kkHMBlDovnS4ZHF5lLslVtqddaqt)\n",
        "\n",
        "それでは、下のセルを実行してみてください。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "z6Qdxi5CGQD3"
      },
      "source": [
        "from google.colab import drive\n",
        "import os\n",
        "\n",
        "# Googleドライブマウント\n",
        "drive.mount('/gdrive')\n",
        "\n",
        "# 起点ディレクトリ\n",
        "base_path = \"/gdrive/My Drive\"\n",
        "\n",
        "! echo \"ルートフォルダの中身 -----------\"\n",
        "! ls \"$base_path\"\n",
        "! echo \"--------------------\"\n",
        "\n",
        "# 入力動画ファイル\n",
        "input_video = base_path + \"/input.mp4\"\n",
        "\n",
        "print(\"ファイル名: \", os.path.basename(input_video))\n",
        "print(\"ファイルサイズ: \", os.path.getsize(input_video))\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fndC3AcISU02"
      },
      "source": [
        "## Googleドライブとの連携（出力）"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3_gAjNdVSZJG"
      },
      "source": [
        "1．読み込みに成功したら、次は出力してみましょう。\n",
        "\n",
        "2．下のセルを実行してみてください。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "TMcUl9vmShcq"
      },
      "source": [
        "from google.colab import drive\n",
        "import os\n",
        "\n",
        "# Googleドライブマウント\n",
        "drive.mount('/gdrive')\n",
        "\n",
        "# 起点ディレクトリ\n",
        "base_path = \"/gdrive/My Drive\"\n",
        "\n",
        "with open(base_path +'/output.txt', 'w') as f:\n",
        "  f.write('Google Drive 出力成功')\n",
        "\n",
        "! echo \"フォルダの中身 -----------\"\n",
        "! ls -l \"$base_path\"\n",
        "! echo \"--------------------\"\n",
        "\n",
        "! cat \"$base_path/output.txt\"\n"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s4TyEe9BTXkM"
      },
      "source": [
        "3．下の画像のように出力されていれば成功です。\n",
        "\n",
        "![dirve output](https://drive.google.com/uc?export=view&id=1dNEPMLJ20wzP7I7xczSL0Otmu5PHqBWL)\n",
        "\n",
        "4．Googleドライブの ルートフォルダを確認すると、\"output.txt\"ファイルが出来ているはずです。\n",
        "\n",
        "![Googleドライブ](https://drive.google.com/uc?export=view&id=1UZ9WxOK8v2j2CTuDFnKknXrtnNv4gWN8)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GEzYaF9ndNm_"
      },
      "source": [
        "**注意点**\n",
        "\n",
        "Googleドライブに保存したデータは「削除」を実行しても、ゴミ箱に移動するだけです。\n",
        "\n",
        "このままでは、容量は減らないので、容量を減らすためには、「ゴミ場を空にする」を実行して下さい。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "69OX4JAfOpeq"
      },
      "source": [
        "# MMD用背景生成処理"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "1Mcy8OsBOlJE",
        "cellView": "form"
      },
      "source": [
        "#@markdown ■■■■■■■■■■■■■■■■■■\n",
        "\n",
        "#@markdown 動画を変換する際のパラメーターを入力して、セルを実行して下さい。\n",
        "\n",
        "#@markdown ■■■■■■■■■■■■■■■■■■\n",
        "\n",
        "#@markdown --- \n",
        "\n",
        "#@markdown ### 入力映像ファイル\n",
        "#@markdown 解析対象となる映像のファイルの名前を入力して下さい。\n",
        "\n",
        "#@markdown mp4、avi他、拡張子は不問です。\n",
        "\n",
        "#@markdown 30fpsではない場合、再エンコードします。\n",
        "\n",
        "input_video_name = \"input.mp4\"  #@param {type: \"string\"}\n",
        "\n",
        "#@markdown --- \n",
        "\n",
        "#@markdown ### 出力AVIサイズ\n",
        "#@markdown 出力するAVIのサイズ（大きさ）を指定して下さい。\n",
        "\n",
        "#@markdown 上限1GBの場合、映像の大きさを自動調整します。(エンコードの都合上、ファイルサイズの上限が1GBとなっています。MMDは本来2GBまで)\n",
        "\n",
        "output_avi_size = \"MMD\\u306B\\u76F4\\u63A5\\u8AAD\\u307F\\u8FBC\\u3081\\u308B\\u30B5\\u30A4\\u30BA(\\u4E0A\\u96501GB)\" #@param [\"MMDに直接読み込めるサイズ(上限1GB)\", \"オリジナルと同じサイズ\"] {allow-input: false}\n",
        "\n",
        "from google.colab import drive\n",
        "import sys\n",
        "import os\n",
        "import cv2\n",
        "import datetime\n",
        "import math\n",
        "import glob \n",
        "import re\n",
        "from tqdm import tqdm\n",
        "from PIL import Image\n",
        "import traceback\n",
        "import numpy as np\n",
        "import shutil\n",
        "\n",
        "# Googleドライブマウント\n",
        "drive.mount('/gdrive')\n",
        "\n",
        "# 起点ディレクトリ\n",
        "base_path = \"/gdrive/My Drive\"\n",
        "\n",
        "# 入力動画ファイル\n",
        "input_video = base_path + \"/\"+ input_video_name\n",
        "\n",
        "def numericalSort(value):\n",
        "    numbers = re.compile(r'(\\d+)')\n",
        "    parts = numbers.split(value)\n",
        "    parts[1::2] = map(int, parts[1::2])\n",
        "    return parts\n",
        "\n",
        "if not os.path.exists(input_video) or not os.path.isfile(input_video):\n",
        "    print(\"■■■■■■■■■■■■■■■■■■■■■■■■\")\n",
        "    print(\"■■ 指定入力映像ファイルが見つかりませんでした\")\n",
        "    print(\"■■\")\n",
        "    print(\"■■ ファイル名: %s\" % input_video_name)\n",
        "    print(\"■■■■■■■■■■■■■■■■■■■■■■■■\")\n",
        "else:\n",
        "    try:\n",
        "        print(\"ファイル名: \", os.path.basename(input_video))\n",
        "        print(\"ファイルサイズ: \", os.path.getsize(input_video))\n",
        "\n",
        "        video = cv2.VideoCapture(input_video)\n",
        "        # 幅\n",
        "        W = video.get(cv2.CAP_PROP_FRAME_WIDTH)\n",
        "        # 高さ\n",
        "        H = video.get(cv2.CAP_PROP_FRAME_HEIGHT)\n",
        "        # 総フレーム数\n",
        "        count = video.get(cv2.CAP_PROP_FRAME_COUNT)\n",
        "        # fps\n",
        "        fps = video.get(cv2.CAP_PROP_FPS)\n",
        "        # 秒数\n",
        "        video_len_sec = count / fps\n",
        "        # アスペクト\n",
        "        aspect = W / H\n",
        "\n",
        "        # I420のピクセルサイズ\n",
        "        width = W if output_avi_size == \"オリジナルと同じサイズ\" else min(W, 100000000 / video_len_sec / 30 / 16 / 1.4 / aspect)\n",
        "        print(\"横: {0}, 縦: {1}, フレーム数: {2}, fps: {3}, 秒数: {4}\".format(W, H, count, fps, video_len_sec))\n",
        "\n",
        "        size_rate = width / W\n",
        "        width = int(width)\n",
        "        height = int(H * size_rate)\n",
        "\n",
        "        # 画面比率\n",
        "        aspect = width / height\n",
        "\n",
        "        if width != W or height != H or fps != 30 or \".avi\" not in input_video_name:\n",
        "            print(\"【エンコード後】横: {0}, 縦: {1}, 縮小比率: {2}\".format(width, height, size_rate))\n",
        "            print(\"大きさ・fps・拡張子のいずれかが処理対象外のため、再エンコードします: %s\" % (input_video))\n",
        "            \n",
        "            if os.path.exists(\"./resize_img/\"):\n",
        "                shutil.rmtree(\"./resize_img/\")\n",
        "\n",
        "            if os.path.exists(\"./interpolation_img/\"):\n",
        "                shutil.rmtree(\"./interpolation_img/\")\n",
        "\n",
        "            # フォルダ生成\n",
        "            os.mkdir(\"./resize_img/\")\n",
        "            os.mkdir(\"./interpolation_img/\")\n",
        "\n",
        "            # リサイズpng出力先\n",
        "            resize_img_path = \"./resize_img/resize{:012}.png\"\n",
        "            # 補間png出力先\n",
        "            interpolation_output_path = \"./interpolation_img/interpolation_{:012}.png\"\n",
        "\n",
        "            # 縮尺\n",
        "            scale = width / W\n",
        "\n",
        "            # オリジナル高さ\n",
        "            im_height = int(H * scale)\n",
        "\n",
        "            # 出力ファイルパス\n",
        "            out_name = 'recode_{0}.avi'.format(\"{0:%Y%m%d_%H%M%S}\".format(datetime.datetime.now()))\n",
        "            out_path = '{0}/{1}'.format(base_path, out_name)\n",
        "            cnt = 0\n",
        "\n",
        "            try:\n",
        "                fourcc = cv2.VideoWriter_fourcc(*\"I420\")\n",
        "                out = cv2.VideoWriter(out_path, fourcc, 30.0, (width, height))\n",
        "                # 入力ファイル\n",
        "                cap = cv2.VideoCapture(input_video)\n",
        "\n",
        "                print (\"■■■■■元動画読み込み開始\")\n",
        "\n",
        "                for n in range(int(count)):\n",
        "                # for _ in tqdm(range(int(count))):\n",
        "                    if not cap.isOpened():\n",
        "                        break\n",
        "\n",
        "                    # 動画から1枚キャプチャして読み込む\n",
        "                    flag, frame = cap.read()  # Capture frame-by-frame\n",
        "\n",
        "                    # 動画が終わっていたら終了\n",
        "                    if flag == False:\n",
        "                        break\n",
        "\n",
        "                    # 画像の縦横を指定サイズに変形\n",
        "                    img = Image.fromarray(frame)\n",
        "                    img = img.resize((width, im_height),Image.ANTIALIAS)\n",
        "\n",
        "                    # 黒く塗りつぶす用の背景画像を作成\n",
        "                    bg = Image.new(\"RGB\",[width,height],(0,0,0))\n",
        "\n",
        "                    # 元の画像を、背景画像のセンターに配置\n",
        "                    bg.paste(img,(int((width-img.size[0])/2),int((height-img.size[1])/2)))\n",
        "\n",
        "                    # opencv用に変換\n",
        "                    out_frame = np.asarray(bg)\n",
        "\n",
        "                    # PNG出力\n",
        "                    cv2.imwrite(resize_img_path.format(n), out_frame)\n",
        "\n",
        "                    if n % 100 == 0:\n",
        "                        print (\"-- 元動画読み込み中(%s)\" % n)\n",
        "\n",
        "                print (\"■■■■■元動画読み込み終了\")\n",
        "\n",
        "                # フレーム補間用比率\n",
        "                fps_interpolation = fps / 30\n",
        "\n",
        "                print(\"■■■■■補間生成開始\")\n",
        "                cnt = 0\n",
        "\n",
        "                # 最後の１つ手前（補間ができる状態）までループ\n",
        "                # for _ in tqdm(range(round(count * (30 / fps)) - 1)):\n",
        "                for _ in range(round(count * (30 / fps)) - 1):\n",
        "                    # 補間した出力CNT\n",
        "                    inter_cnt = cnt * fps_interpolation\n",
        "                    # INDEXと比率（整数部と小数部）\n",
        "                    inter_cnt_rate, inter_cnt_idx = math.modf(inter_cnt)\n",
        "                    # print(\"フレーム補間: %s -> %s, idx: %s, rate: %s\" % ( cnt, inter_cnt, inter_cnt_idx, inter_cnt_rate ))\n",
        "\n",
        "                    # 前のフレーム\n",
        "                    past_frame = cv2.imread(resize_img_path.format(int(inter_cnt_idx)))\n",
        "                    # 今回のフレーム\n",
        "                    now_frame = cv2.imread(resize_img_path.format(int(inter_cnt_idx + 1)))\n",
        "\n",
        "                    # 混ぜ合わせる比率\n",
        "                    past_rate = inter_cnt_rate\n",
        "                    now_rate = 1 - inter_cnt_rate\n",
        "\n",
        "                    # フレーム補間をして出力する\n",
        "                    target_output_frame = cv2.addWeighted(past_frame, past_rate, now_frame, now_rate, 0)\n",
        "\n",
        "                    # PNG出力\n",
        "                    cv2.imwrite(interpolation_output_path.format(cnt), target_output_frame)\n",
        "\n",
        "                    cnt += 1\n",
        "\n",
        "                    if cnt % 100 == 0:\n",
        "                        print (\"-- 補間生成中(%s)\" % cnt)\n",
        "\n",
        "                print(\"■■■■■補間生成終了\")\n",
        "\n",
        "                # 最後にnowを出力\n",
        "                cv2.imwrite(interpolation_output_path.format(cnt), now_frame)\n",
        "\n",
        "                print(\"■■■■■結合開始\")\n",
        "\n",
        "                # 結合開始\n",
        "                for n, file_path in enumerate(sorted(glob.glob('./interpolation_img/interpolation_*.png'), key=numericalSort)):\n",
        "                    # フレーム\n",
        "                    frame = cv2.imread(file_path)\n",
        "\n",
        "                    # 動画出力\n",
        "                    out.write(frame)\n",
        "\n",
        "                    if n % 100 == 0:\n",
        "                        print (\"-- 結合中(%s)\" % n)\n",
        "\n",
        "                print(\"■■■■■結合終了\")\n",
        "\n",
        "                # 終わったら開放\n",
        "                out.release()\n",
        "                cap.release()\n",
        "            except Exception as e:\n",
        "                print(\"再エンコード失敗\", e)\n",
        "                print(traceback.format_exc())\n",
        "\n",
        "            cv2.destroyAllWindows()\n",
        "            \n",
        "            print('MMD入力用MP4ファイル再生成', out_path)\n",
        "            input_video_name = out_name\n",
        "\n",
        "            # 入力動画ファイル再設定\n",
        "            input_video = base_path + \"/\"+ input_video_name\n",
        "            \n",
        "            video = cv2.VideoCapture(input_video)\n",
        "            # 幅\n",
        "            W = video.get(cv2.CAP_PROP_FRAME_WIDTH)\n",
        "            # 高さ\n",
        "            H = video.get(cv2.CAP_PROP_FRAME_HEIGHT)\n",
        "            # 総フレーム数\n",
        "            count = video.get(cv2.CAP_PROP_FRAME_COUNT)\n",
        "            # fps\n",
        "            fps = video.get(cv2.CAP_PROP_FPS)\n",
        "\n",
        "            print(\"【再チェック】横: {0}, 縦: {1}, フレーム数: {2}, fps: {3}\".format(W, H, count, fps))\n",
        "\n",
        "        !echo \"変換後映像ファイルは\" \"$input_video_name\" \"です。\"\n",
        "\n",
        "    except Exception as e:\n",
        "        print(\"動画読み取り失敗\", e)\n",
        "        print(traceback.format_exc())\n"
      ],
      "execution_count": null,
      "outputs": []
    }
  ]
}